<?php

/**
 * supply database, table and method to get user logge-in info
 *
 * @package SGN_Core
 * @version $Id: Account.php 1658 2012-04-04 02:13:55Z locth $
 * @author $Author: locth $
 *
 */
class SGN_Account
{
    /**
     * Zing ID Account Name
     *
     * @var string
     */
    private $_accountName = '';

    /**
     * table name of account
     *
     * @var string
     */
    private $_tableName = '';
    /**
     * db name of account
     *
     * @var string
     */
    private $_dbName = '';

    /**
     * SGN Personal Info
     *
     * @var array
     */
    private $_accountInfo = array();

    /**
     * vPortal Cache Object
     *
     * @var object
     */
    private $_cache = null;
    /**
     * Local Cache properties
     *
     * @var unknown_type
     */
    private $_localCache = array();
    /**
     * Cache prefix
     *
     * @var string
     */
    private $_cachePrefixes = 'account';
    /**
     * local self property to store itself
     * 
     * @var SGN_Account
     */
    static $_self = array();
    /**
     *
     * Constructor
     *
     */
    public function __construct($accountName = '', $isPrepare = true, $product = 'gunny')
    {
        // prepare database object & cache object
        $this->_cache = SGN_Application::getCacheInstance('sgn', 'account');
        $this->_conn = SGN_Application::getDbInstance('sgn', 'profile');
        if($accountName){
            $this->initAccountInfo($accountName, $isPrepare);
        }
    }
    /**
     * Initialize account information upon receiving account name 
     * 
     * @param string $accountName
     * 
     * @return void
     */
    public function initAccountInfo($accountName, $isPrepare = true)
    {
        $this->setAccountName($accountName);
        $this->hash($accountName);
        if($isPrepare){
            $this->_accountInfo = $this->getAccountInfo();
        }
    }
    /**
     * Factory method
     * 
     * @return SGN_Account
     * 
     */
    public static function factory ($userID)
    {
        if (self::$_self['self'][$userID] instanceof SGN_Account) {
            return self::$_self['self'][$userID];
        }
        self::$_self['self'][$userID] = new self();
        $accountName = self::$_self['self'][$userID]->getAccountName($userID);
        self::$_self['self'][$userID]->initAccountInfo($accountName, 1);
        return self::$_self['self'][$userID];
    } 
    /**
     * Verify if a character belongs to this account
     * 
     * @return boolean
     */
    public function verifyCharOfAccount($charId, $gameCode)
    {
        $characterArray = $this->getAccountGameInfo($gameCode);
        return in_array($charId, $characterArray);
    }
    /**
     * Get Account Name
     *
     * @param integer $userid
     *
     * @return string
     */
    public function getAccountName($userID = 0)
    {
        if (!$userID) {
            return $this->_accountName;
        }
        $key = $this->_cachePrefixes . '.userid.' . $userID;
        // get username by userid
        $accountName = $this->_cache->load($key);
        if ($accountName === false) {
            // get userinfo (saved by update/register) to get username by userid 
            $keyInfo = $this->_cachePrefixes . '.accountRegistered.' . $userID;
            $userinfo = $this->_cache->load($keyInfo);
            if ($userinfo === false) {
                $sql = "SELECT accountName
                    FROM sgn_global.AccountID
                    WHERE id = ?";
                $accountName = $this->_conn->fetchOne($sql, $userID);
                // save userid with accountname
                $this->_cache->save($userID, 'accountRegisteredIdName.' . $accountName);
                // save username with userid
                $this->_cache->save($accountName, $key);
            } else {
                $accountName = $userinfo['accountName'];
            }
        }
        return $accountName;
        
    }
    /**
     * Get SGN Account's ID
     * 
     * @return integer 
     */
    public function getUserId($accountName = '')
    {
        if (!$accountName) {
            return $this->_accountInfo['userID'];
        }
        $key = $this->_cachePrefixes . '.username.' . $accountName;
        // get username by userid
        $userId = $this->_cache->load($key);
        if ($userId === false) {
            $sql = "SELECT id
                FROM sgn_global.AccountID
                WHERE accountName = ?";
            $userId = $this->_conn->fetchOne($sql, $accountName);
            // save userid with accountName
            $this->_cache->save($userId, $key);
        }
        return $userId;
    }
    /**
     * Get ServerCode from Account's Game Info using $userID and $characterId
     * 
     * @param integer $userID
     * @param integer $charId
     * 
     * @return string
     */
    public function getServerCode($userID, $charId, $gameId)
    {
        return array_search($charId, $this->getAccountGameInfo($gameId));
    }
    /**
     *
     * Set Account Name
     *
     * @return string
     */
    public function setAccountName($accountName)
    {
        /*
        if (!preg_match('#^[A-z0-9]+$#s', $this->_accountName)) {
            $accountName = md5($accountName);
        }
        */
        $this->_accountName = $accountName;
        $this->hash($this->_accountName);
        //$this->_accountInfo = $this->getAccountInfo();
    }

    /**
     *
     * get zing me account info
     *
     * @return string
     */
    public function getZingMeUserInfo()
    {
        if ($this->_localCache['getZingMeUserInfo'][$this->_accountName]) {
            return $this->_localCache['getZingMeUserInfo'][$this->_accountName];
        }
        // check input
        if( !isset( $this->_accountName[0] ) ){
            return false;
        }

        $key = $this->_cachePrefixes . '.' . __FUNCTION__ . '.' . $this->_accountName;
        //get data from cache
        $userInfo = $this->_cache->load($key);
        if(( !empty($userInfo) )){
            $result[$key] = $userInfo;
            return $userInfo;
        }

        // prepare data to connect to service
        $config = SGN_Application::getConfig('sgn', 'services', 'getZingMeUserInfo');
        $service = new SGN_SoapClient($config->address);
        $dataInput = array(  'accountName' => $this->_accountName,
                             'gameCode' => $config->product );
        try {
            $response = $service->requestFunction( $config->method, $dataInput, $config->key );
            if ($response[0] == 1) {
                $userInfo = $response[1][0];
                $this->_localCache['getZingMeUserInfo'][$this->_accountName] = $userInfo;
                $this->_cache->save($userInfo, $key);
            }
        } catch (Exception $e) {
            // write error log
            // TODO
        }
        return $this->_localCache['getZingMeUserInfo'][$this->_accountName];
    }

    /**
     *
     * Get passport info
     *
     * @return string
     */
    public function getPassportInfo()
    {
        $result = array();
        static $result;

        // check input
        if( !isset( $this->_accountName[0] ) ){
            return false;
        }

        $key = $this->_cachePrefixes . '.' . __FUNCTION__ . '.' . $this->_accountName;
        if (isset($result[$key])) {
            return $result[$key];
        }

        //get data from cache
        $userInfo = $this->_cache->load($key);
        if(( !empty($userInfo) )){
            $result[$key] = $userInfo;
            return $userInfo;
        }

        // prepare data to connect to service
        $config = SGN_Application::getConfig('sgn', 'services', 'getPassportInformation');
        $service = new SGN_SoapClient($config->address);
        $dataInput = array( 'accountPassport' => $this->_accountName );
        try {
            $response = $service->requestFunction( $config->method, $dataInput, $config->key );
            if ($response[0] == 1) {
                $userInfo = $response[1];
                $result[$key] = $userInfo;
                $this->_cache->save($userInfo, $key);
            }
        } catch (Exception $e) {
            // write error log
            // TODO
        }
        return $userInfo;
    }

    /**
     *
     * Get Account Info
     *
     * @return string
     */
    public function getAccountInfo()
    {
        if ($this->_localCache['_accountInfo'][$this->_accountName]) {
            return $this->_localCache['_accountInfo'][$this->_accountName];
        }
        if(empty($this->_accountInfo)){
            //get account id from account name
            $accountId = $this->getUserId($this->_accountName);
            if (!$accountId) {
                return;
            }
            //get account info from id
            $key = $this->_cachePrefixes . '.accountRegistered.' . $accountId;
            //get data from cache
            $this->_accountInfo = $this->_cache->load($key);
            if ($this->_accountInfo === false) {
                $sql = "SELECT userID, accountName, profileName, fromProduct, mainCharacterServerId, mainCharacterName, mainCharacterId, lastLoginDate, registerDate
                    FROM SGN_Account{$this->_dbName}.account{$this->_tableName}
                    WHERE accountName = ? ";
                $this->_accountInfo = $this->_conn->fetchRow($sql, $this->_accountName);

                $this->_cache->save($this->_accountInfo, $key);
                $this->_cache->save($this->_accountInfo['userID'],
                                    'accountRegisteredIdName.' . $this->_accountInfo['accountName']);
            }
        }
        if ($this->_accountInfo['lastLoginDate']) {
            $this->_accountInfo['lastLoginDate'] = SGN_Application::formatTime($this->_accountInfo['lastLoginDate']);
        }
        $this->_accountInfo['registerDate'] = SGN_Application::formatTime($this->_accountInfo['registerDate']);
        $this->_localCache['_accountInfo'][$this->_accountName] = $this->_accountInfo;
        return $this->_accountInfo;
    }

    /**
     *
     * Get Account Detail
     *
     * @return string
     */
    public function getAccountDetail($gameCode = 'gunny')
    {
        $config = SGN_Application::getConfig('sgn', 'global', 'gameId')->toArray();
        return $this->getAccountGameInfo($config[$gameCode]);
    }
    /**
     * Get Account Game Info
     * Game Info is info of which game user is playing and character id of each server
     *
     * @param integer $gameId
     *
     * @return array
     */
    public function getAccountGameInfo($gameId)
    {
        if ($this->_localCache['accountGameInfo'][$gameId][$this->_accountName]){
            return $this->_localCache['accountGameInfo'][$gameId][$this->_accountName];
        }
        $key = $this->_cachePrefixes . '.accountGameInfo.' . $this->_accountName;
        //get data from cache

        $gameInfo = $this->_cache->load($key);
        if ($gameInfo === false) {
            $sql = "SELECT GameInfo
               FROM SGN_Account{$this->_dbName}.accountGameInfo{$this->_tableName}
               WHERE accountName = ? AND GameID = ?";
            $gameInfo = $this->_conn->fetchOne($sql, array($this->_accountName, $gameId));
            $gameInfo = unserialize($gameInfo);
            if (!$gameInfo) {
                $gameInfo = '1';
            }
            $this->_localCache['accountGameInfo'][$gameId][$this->_accountName] = $gameInfo;
            $this->_cache->save($gameInfo, $key);
        }
        return $gameInfo;
    }

    /**
     *
     * Update Account Game Info
     * Game Info is info of which game user is playing and character id of each server
     *
     * @param array $accountInfo => (accountName, GameID, array(gameInfo))
     *
     * @return boolean
     */
    public function updateAccountGameInfo($accountInfo)
    {
        // check input
        if( !isset($accountInfo['accountName'][0]) ||
            !is_numeric($accountInfo['GameID']) ||
            !is_array($accountInfo['gameInfo']) ||
            count($accountInfo['gameInfo']) < 1 ||
            !isset($this->_dbName[0]) ||
            !isset($this->_tableName[0])){

            return false;
        }

        // insert new characters of account
        $accountInfo['gameInfo'] = serialize($accountInfo['gameInfo']);
        $result = $this->_conn->insert("SGN_Account{$this->_dbName}.accountGameInfo{$this->_tableName}", $accountInfo);
        return $result;
    }

    /**
     * Create SGN Account
     *
     * @param integer $characterId
     * @param string $characterName
     * @param string $profileName
     * @param integer $serverId
     * @param string $product
     *
     * @return boolean
     */
    public function createAccount($characterId, $characterName='', $profileName = '', $serverId, $product = 'ZG')
    {
        if ($this->checkAccountExist()) {
            return false;
        }
        //use passport id
        $userinfo['userID']             = $this->_userID = intval(substr($_COOKIE['uin'], 1));
        $userinfo['accountName']        = $this->_accountName;
        $userinfo['fromProduct']        = $product;
        $userinfo['registerDate']       = SGN_Application::getCurrentTime();
        $userinfo['lastLoginDate']      = SGN_Application::getCurrentTime();
        $userinfo['mainCharacterGame']  = '';
        $userinfo['mainCharacterServerId'] = $serverId;
        $userinfo['mainCharacterName']  = $characterName;
        $userinfo['mainCharacterId']    = $characterId;
        $userinfo['achievementPoint']   = 0;
        $userinfo['profileName']        = $profileName;
        
//         $count = $this->_conn->insert("SGN_Account" . $this->_dbName . ".account" . $this->_tableName, $userinfo);
//         SGN_Log::writeSimpleLog(implode("\t", $userinfo) . "\n", 3, '/home/sgn/logs/debug.log');
        /*// get userid because accounts are hashed into databases, auto increment doesn't work
        $count = $this->_conn->insert('sgn_global.AccountID', array('accountName' => $this->_accountName, 'dateline' => SGN_Application::getCurrentTime()));
        if($count > 0){
            $this->_userID = $this->_conn->lastInsertId();

            // insert new account
            if($this->_userID > 0){
                $count = $this->_conn->insert("SGN_Account" . $this->_dbName . ".account" . $this->_tableName, $userinfo);
            }
        }*/

        // prepare cache to show
        //$key = $this->_cachePrefixes . '.accountRegistered.' . $this->_accountName;
        $key = $this->_cachePrefixes . '.accountRegistered.' . $this->_userID;
        $this->_cache->save($userinfo, $key);
        $this->_cache->save($this->_userID,
                            'accountRegisteredIdName.' . $this->_accountName);
        $this->_accountInfo = $userinfo;

        return $count;
    }

    /**
     * Update SGN Account
     *
     *
     *
     *
     */
    public function updateAccount(  $mainCharacterId = null,
                                    $mainCharacterName = null,
                                    $profileName = null,
                                    $serverId = null,
                                    $product='ZG')
    {
        if( !is_numeric($this->_accountInfo['userID']) ){
            return false;
        }
        $condition['userID = ?'] = $this->_accountInfo['userID'];
        $condition['fromProduct = ?'] = $product;
        $userinfo = $this->_accountInfo;
        $userinfo['lastLoginDate'] = SGN_Application::getCurrentTime();
        $userinfo['fromProduct'] = $product;

        if($mainCharacterName !== null){
            $userinfo['mainCharacterName'] = trim($mainCharacterName);
        }

        if($mainCharacterId !== null){
            $userinfo['mainCharacterId'] = $mainCharacterId;
        }

        if(isset($profileName[0])){
            $userinfo['profileName'] = $profileName;
        }

        if(is_numeric($serverId) && $serverId > 0){
            $userinfo['mainCharacterServerId'] = $serverId;
        }

        #set to queue
        $accountInfo = array('data' => $userinfo,
                             'account' => array('dateline' => SGN_Application::getCurrentTime()),
                             'condition' => $condition,
                             'data_idx' => "SGN_Account" . $this->_dbName . ".account" . $this->_tableName,
                             'flag' => 2);// 1: insert; 2: update
        $queueConfig = SGN_Application::getConfig('sgn', 'queue', 'accountUpdate')->toArray();
        $mqueue = new Zend_Queue('MemcacheQ', $queueConfig);
        if (! $mqueue->getAdapter()->isExists($queueConfig['name'])) {
            $mqueue = $mqueue->createQueue($queueConfig['name']);
        }
        $mqueue->send(serialize(array()));
        $mqueue->send(serialize($accountInfo));
        #end queue

        //$key = $this->_cachePrefixes . '.accountRegistered.' . $this->_accountName;
        $key = $this->_cachePrefixes . '.accountRegistered.' . $this->_accountInfo['userID'];
        $this->getAccountInfo();
        $this->_accountInfo = $userinfo;
        $this->_cache->save($this->_accountInfo, $key);
        //$this->_conn->update("SGN_Account" . $this->_dbName . ".account" . $this->_tableName, $userinfo, $condition);
        return true;
    }

    /**
     *
     * Check if an account exists in SGN DB
     *
     * @return boolean
     */
    public function checkAccountExist()
    {
        if ($this->_accountInfo['accountName']) {
            return true;
        }
        return false;
    }

    public static function logout()
    {
        /*
         * Clear session
         */
        $session = SGN_Application::get('session');
        $session->set('username', null);
        $session->set('loggedin', null);
        $session->set('uin', null);

        /*
         * Clear cookie
         */
        SGN_Application::setCookie('code', '');
        SGN_Application::setCookie('acn', '');
        SGN_Application::setCookie('uin', '');
        SGN_Application::setCookie('vngauth', '');
        SGN_Application::setCookie(APPLICATION_ENV.'_username', null);
        SGN_Application::setCookie(APPLICATION_ENV.'_loggedin', null);
    }

    public static function checkLogin($getSession = true, $relogin = false)
    {
        $session = SGN_Application::get('session');
        if ($getSession) {
            $accountName = $session->get('username');
            $account = new SGN_Account($accountName);
            SGN_Application::set('account', $account);
            if($session->get('loggedin') == 1 && $accountName){
                return true;
            }
        }

        if( $relogin ||
            (($session->get('loggedin') == '' || !$accountName) &&
            (!$getSession || (SGN_Application::getCookie('uin') &&
            SGN_Application::getCookie('code') &&
            SGN_Application::getCookie('vngauth')))))
        {
            //need to check SSO
            $accountName = SGN_SSO::validate(SGN_Application::getConfig('sgn', 'sso'), $getSession);

            //logged in SSO
            if($accountName){
                //restore session
                $session->set('loggedin', 1);
                $session->set('username', $accountName);
                $account = new SGN_Account($accountName);
                SGN_Application::set('account', $account);

                //rewrite cookie
                SGN_Application::setCookie(APPLICATION_ENV.'_loggedin', 1);
                SGN_Application::setCookie(APPLICATION_ENV.'_username', $accountName);
                return true;
            }
        }

        return false;
    }

    /**
     *
     * Hash account into 256 tables, 8 databases
     *
     * @param string $accountName
     * @return array
     */
    public function hash($accountName)
    {
        $total = 0;
        $array = str_split($accountName);
        foreach ($array as $char) {
            $total = 5 * $total % 256 + ord($char);
        }
        $this->_tableName = sprintf('%05d', $total % 256);
        $this->_dbName = sprintf('%02d', floor($this->_tableName / 32));
        return array('table' => $this->_tableName, 'db' => $this->_dbName);
    }

    public function getCharacterList(&$accountInfo)
    {
        $result = array();

        // load model
        $model = Application_Model_Armory_Gunny::factory('Character');

        // get account detail, include: (number)user id (SGN), (string)server id : character id, (string)game
        $accountDetail = $this->getAccountDetail();

        // get all characters from all servers by account name (using cache-updated of search-indexed)
        $characters = array();
        $accountInfo = array();
        if(is_array($accountDetail) && count($accountDetail) > 0){
            foreach($accountDetail as $server => $characterId){
                //check input
                if( !isset($server[0]) || empty($characterId) ){
                    continue;
                }

                $character = $model->getPreCachedCharacter($model->armoryConfig['serverInfo'][$server]['id'], $characterId);
                if(empty($character)){
                    continue;
                }
                $character['Server'] = $server;
                $character['serverName'] = $model->armoryConfig['serverInfo'][$server]['name'];
                $characters[] = $character;

                // return account detail
                $accountInfo[$server] = $characterId;
            }

            // and sort by level
            usort($characters, 'Application_Model_Armory_Gunny::sortLevel');
            $characters = array_reverse($characters, true);
        }

        /*
         * get all level >= 10
         * if no character with level >= 0 then get first character
         */
        if(is_array($characters)){
            reset($characters);
            do{
                $result[] = current($characters);
                $next = next($characters);
            }
            while($next !== false && $next['Grade'] >= 10);
        }

        // sort by fight power
        usort($result, 'Application_Model_Armory_Gunny::sortFightPower');
        $result = array_reverse($result, true);
        return $result;
    }

    public function getDefaultCharacter(&$profileInfo)
    {
        $result = array();

        //get default character from account DB
        $profileInfo = $this->getAccountInfo();

        // load model
        $model = Application_Model_Armory_Gunny::factory('Character');

        //get index of default character
        $updateFlg = false;
        $result['all'] = array();
        if( is_numeric($profileInfo['mainCharacterServerId']) &&
            $profileInfo['mainCharacterServerId'] > 0)
        {
            $server = $model->armoryConfig['serverId'][$profileInfo['mainCharacterServerId']];
            $character = $model->getPreCachedCharacter($profileInfo['mainCharacterServerId'], $profileInfo['mainCharacterId']);
            if(!isset($character['UserID'])){
                $character = $character[0];
            }
            $character['Server'] = $server;
            $character['serverName'] = $model->armoryConfig['serverInfo'][$server]['name'];
            $result['all'][] = $character;
        }

        $updateAccountDetail = false;
        $characters = array();
        if(!isset($result['all'][0])){
            //get all characters from sys_user
            $result['all'] = &$this->getCharacterList($characters);
            $updateFlg = true;
        }

        //get default character from list of characters
        reset($result['all']);
        $defaultIdx = key($result['all']);

        //update profile (if any)
        if($updateFlg){
            //insert/updte profile using queue
            $this->register($result['all'][$defaultIdx]['UserID'],
                             $result['all'][$defaultIdx]['NickName'],
                             $this->getAccountName(),
                             $model->armoryConfig['serverInfo'][$result['all'][$defaultIdx]['Server']]['id']);

            //get default character from account DB
            $profileInfo = $this->getAccountInfo();
        }

        //set return
        $result['default'] = $defaultIdx;
        return $result;
    }

    public function register( $profileName,
                              $characterId = null,
                              $characterName = null,
                              $serverId = null)
    {
        $result = false;

        //insert
        $result = $this->createAccount($characterId, $characterName, $profileName, $serverId);
        if(!$result){
            //update
            $result = $this->updateAccount($characterId, $characterName, $profileName, $serverId);
        }
        return $result;
    }
}
